package filex

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"gitee.com/zhongguo168a/gocodes/datax/arrayx"
	"github.com/pkg/errors"
)

// IFileInfo os.FileInfo的扩展
type IFileInfo interface {
	os.FileInfo

	Path() string

	// Depth 使用过滤器后产生的相对于过滤器根目录的深度
	Depth() int
	// 设置深度
	setDepth(val int)
}

// MustFileInfo 必须获得FileInfo，否则异常
func MustFileInfo(path string) (obj *FileInfoX) {
	var (
		err error
	)
	obj, err = NewFileInfo(path)
	if err != nil {
		panic(errors.Wrap(err, "new dir info"))
	}

	return
}

// NewFileInfo 新建一个FileInfo
func NewFileInfo(path string) (obj *FileInfoX, err error) {
	obj = &FileInfoX{}
	obj.path = filepath.ToSlash(filepath.Clean(path))
	finfo, err := os.Stat(path)
	if err != nil {
		err = errors.Wrap(err, "read dir: "+path)
		return
	}
	obj.FileInfo = finfo

	return
}

type FileInfoX struct {
	os.FileInfo
	// 完整路径
	path string
	// 相对深度, 过滤器使用
	depth int
}

func (i *FileInfoX) Depth() int {
	return i.depth
}

func (i *FileInfoX) setDepth(val int) {
	i.depth = val
}

func (i *FileInfoX) Path() string {
	return i.path
}

func (i *FileInfoX) Children() (children []IFileInfo) {
	list, readerr := ioutil.ReadDir(i.path)
	if readerr != nil {
		panic(errors.Wrap(readerr, "read dir: "+i.path))
	}
	//
	// 递归
	for _, val := range list {
		fi := &FileInfoX{
			FileInfo: val,
			path:     i.path + "/" + val.Name(),
		}
		children = append(children, fi)
	}

	return
}

func NewFilter() (obj *Filter) {
	obj = &Filter{
		depthMax: -1,
	}
	return
}

type Filter struct {
	//
	ignoreDirs []string
	//
	includeFiles []string
	// 深度
	depthMax int
	//
	excludeAllDir bool
	//
	includeSuffix []string
	//
	nameRegexp *regexp.Regexp
	//
	validator func(ifile IFileInfo) bool
}

// 忽略掉[ignoreDirs]指定的目录, 例如.git/.svn, 避免递归遍历
func (f *Filter) IgnoreDir(dir string) *Filter {
	f.ignoreDirs = append(f.ignoreDirs, dir)
	return f
}

func (f *Filter) IncludeFile(file string) *Filter {
	f.includeFiles = append(f.includeFiles, file)
	return f
}

// 递归
func (f *Filter) Recursive() *Filter {
	f.depthMax = -1
	return f
}

// 深度
func (f *Filter) Depth(val int) *Filter {
	f.depthMax = val
	return f
}

func (f *Filter) ExcludeAllDir() *Filter {
	f.excludeAllDir = true
	return f
}

// 指定后缀[.suffix, ...]
func (f *Filter) IncludeSuffix(val ...string) *Filter {
	f.includeSuffix = append(f.includeSuffix, val...)
	return f
}

// 指定后缀[.suffix, ...]
func (f *Filter) MatchName(regexp *regexp.Regexp) *Filter {
	f.nameRegexp = regexp
	return f
}

func (f *Filter) checkIgnore(dirname string) bool {
	for _, val := range f.ignoreDirs {
		ignore := strings.Index(dirname, val)
		if ignore != -1 {
			return true
		}
	}

	return false
}

// 验证器, 验证成功的保留
func (f *Filter) Validator(v func(ifile IFileInfo) bool) *Filter {
	f.validator = v
	return f
}

func (f *Filter) GetFileInfosByPath(path string) []IFileInfo {
	obj, err := NewFileInfo(path)
	if err != nil {
		return []IFileInfo{}
	}
	return f.GetFileInfosByDir(obj)
}

func (f *Filter) GetFileInfosByDir(dir *FileInfoX) []IFileInfo {
	dir.depth = 0
	stack := []IFileInfo{dir}
	//
	var files []IFileInfo
	//
	for {
		if len(stack) == 0 {
			break
		}

		cur := stack[0]
		stack = stack[1:]

		if f.validator != nil {
			if f.validator(cur) == false {
				continue
			}
		}

		if cur.IsDir() {
			// 检查是否要忽略
			if f.checkIgnore(cur.Path()) {
				continue
			}

			if f.excludeAllDir == false {
				files = append(files, cur)
			}

			children := cur.(*FileInfoX).Children()
			depthNew := cur.Depth() + 1
			if f.depthMax != -1 && depthNew > f.depthMax {
				continue
			}
			for _, val := range children {
				val.setDepth(depthNew)
				stack = append(stack, val)
			}
		} else {
			files = append(files, cur)
		}
	}

	// include suffix
	if len(f.includeSuffix) > 0 {
		files = func() (x []IFileInfo) {
			for _, val := range files {
				if val.IsDir() == true {
					continue
				}
				suffix := filepath.Ext(val.Path())
				has := arrayx.Contains(len(f.includeSuffix), func(i int) bool {
					return f.includeSuffix[i] == suffix
				})
				if has {
					x = append(x, val)
				}
			}
			return
		}()
	}
	if len(f.includeFiles) > 0 {
		files = func() (x []IFileInfo) {
			for _, val := range files {
				if val.IsDir() == true {
					continue
				}
				has := arrayx.Contains(len(f.includeFiles), func(i int) bool {
					return f.includeFiles[i] == val.Path()
				})
				if has {
					x = append(x, val)
				}
			}
			return
		}()
	}

	if f.nameRegexp != nil {
		//re := regexp.MustCompile(f.nameRegexp)
		files = func() (x []IFileInfo) {
			for _, val := range files {
				if f.nameRegexp.MatchString(val.Name()) {
					x = append(x, val)
				}
			}
			return
		}()
	}

	return files
}
